#include <bsl_err.h>
#include <bsl_sal.h>
#include <crypt_algid.h>
#include <crypt_eal_md.h>
#include <crypt_eal_provider.h>
#include <crypt_errno.h>
#include <crypt_sm3.h>

#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <cmocka.h>

#include "helper.h"

#define GROUP_TEST_MAX 100

typedef struct {
    char *dataA;
    char *dataB;
    char *dataC;
    char *hash;
} CRYPT_SM3_MultiData;

typedef struct {
    char *in;
    char *out;
} CRYPT_SM3_InOut;

static CRYPT_EAL_LibCtx *CreateLoadLibCtx()
{
    CRYPT_EAL_LibCtx *libctx = CRYPT_EAL_LibCtxNew();
    assert_non_null(libctx);
    assert_int_equal(CRYPT_EAL_ProviderSetLoadPath(libctx, "../../build"), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_ProviderLoad(libctx, BSL_SAL_LIB_FMT_LIBSO, "kae_provider", NULL, NULL), CRYPT_SUCCESS);
    return libctx;
}

static void SDV_KAE_SM3_PROVIDER_LOAD(void **state)
{
    (void)state;
    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_EAL_SM3_API_TC001(void **state)
{
    (void)state;
    uint8_t input[100]; // Any length, for example, 100 bytes.
    uint32_t inLen = sizeof(input);
    uint8_t out[32]; // SM3 digest length is 32.
    uint32_t outLen = sizeof(out);
    uint32_t badOutLen = outLen - 1;
    uint32_t longOutLen = outLen + 1;

    assert_int_equal(CRYPT_EAL_MdGetDigestSize(CRYPT_MD_SM3), outLen);
    CRYPT_EAL_MdCTX *ctx = NULL;

    assert_int_equal(CRYPT_EAL_MdDeinit(ctx), CRYPT_NULL_INPUT);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    assert_non_null(ctx);

    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdUpdate(NULL, input, inLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, NULL, inLen), CRYPT_NULL_INPUT);
    // Hash counting can be performed on empty strings.
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, 0), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, NULL, 0), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdFinal(ctx, NULL, &outLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(NULL, out, &outLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, NULL), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &badOutLen), CRYPT_EAL_BUFF_LEN_NOT_ENOUGH);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &longOutLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdGetId(ctx), CRYPT_MD_SM3);
    assert_int_equal(CRYPT_EAL_MdDeinit(ctx), CRYPT_SUCCESS);

    CRYPT_EAL_MdFreeCtx(ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static const char SDV_KAE_SM3_FUNC_TC001_HEX[] = "1AB21D8355CFA17F8E61194831E81A8F22BEC8C728FEFB747ED035EB5082AA2B";

static void SDV_KAE_SM3_FUNC_TC001(void **state)
{
    const char *expect = *((const char **)state);
    Hex *hex = NewHex();
    assert_int_equal(ConvertHex(expect, hex), 0);

    uint8_t output[CRYPT_SM3_DIGESTSIZE];
    uint32_t outLen = CRYPT_SM3_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sm3Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    assert_non_null(sm3Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(sm3Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(sm3Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SM3_DIGESTSIZE);

    assert_int_equal(outLen, hex->len);
    assert_memory_equal(output, hex->x, outLen);

    FreeHex(hex);
    CRYPT_EAL_MdFreeCtx(sm3Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

#define SDV_KAE_SM3_FUNC_TC002_N 3

static const char *SDV_KAE_SM3_FUNC_TC002_HEX[SDV_KAE_SM3_FUNC_TC002_N][2] = {
    {"", "1AB21D8355CFA17F8E61194831E81A8F22BEC8C728FEFB747ED035EB5082AA2B"},
    {"616263", "66C7F0F462EEEDD9D1F2D46BDC10E4E24167C4875CF2F7A2297DA02B8F4BA8E0"},
    {"61626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364",
     "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732"},
};

static void SDV_KAE_SM3_FUNC_TC002(void **state)
{
    uint8_t output[CRYPT_SM3_DIGESTSIZE];
    uint32_t outLen = CRYPT_SM3_DIGESTSIZE;

    const char **test = (const char **)*state;
    const char *in = test[0];
    const char *ou = test[1];

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(in, msg), 0);
    assert_int_equal(ConvertHex(ou, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sm3Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    assert_non_null(sm3Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(sm3Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(sm3Ctx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(sm3Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SM3_DIGESTSIZE);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(sm3Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_SM3_FUNC_TC003(void **state)
{
    (void)state;

    uint8_t input[5050];
    uint32_t inLenTotal = 0;
    uint32_t inLenBase;
    uint8_t outA[CRYPT_SM3_DIGESTSIZE];
    uint8_t outB[CRYPT_SM3_DIGESTSIZE];
    uint32_t outLen = CRYPT_SM3_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *ctxA = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    CRYPT_EAL_MdCTX *ctxB = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");

    assert_non_null(ctxA);
    assert_non_null(ctxB);

    assert_int_equal(CRYPT_EAL_MdInit(ctxA), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdInit(ctxB), CRYPT_SUCCESS);

    for (inLenBase = 1; inLenBase <= 100; inLenBase++) {
        assert_int_equal(CRYPT_EAL_MdUpdate(ctxA, input + inLenTotal, inLenBase), CRYPT_SUCCESS);
        inLenTotal += inLenBase;
    }

    assert_int_equal(CRYPT_EAL_MdFinal(ctxA, outA, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SM3_DIGESTSIZE);

    assert_int_equal(CRYPT_EAL_MdUpdate(ctxB, input, inLenTotal), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctxB, outB, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SM3_DIGESTSIZE);

    assert_memory_equal(outA, outB, outLen);

    CRYPT_EAL_MdFreeCtx(ctxA);
    CRYPT_EAL_MdFreeCtx(ctxB);
    CRYPT_EAL_LibCtxFree(libctx);
}

static CRYPT_SM3_MultiData SDV_KAE_SM3_FUNC_TC004_VEC[] = {
    {
        "616263",
        "6461626364616263646162636461626364616263646162636461626364",
        "6162636461626364616263646162636461626364616263646162636461626364",
        "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732",
    },
};

static void SDV_KAE_SM3_FUNC_TC004(void **state)
{
    CRYPT_SM3_MultiData *test = (CRYPT_SM3_MultiData *)*state;

    Hex *data1 = NewHex();
    Hex *data2 = NewHex();
    Hex *data3 = NewHex();
    Hex *hash = NewHex();

    assert_int_equal(ConvertHex(test->dataA, data1), 0);
    assert_int_equal(ConvertHex(test->dataB, data2), 0);
    assert_int_equal(ConvertHex(test->dataC, data3), 0);
    assert_int_equal(ConvertHex(test->hash, hash), 0);

    uint8_t out[32]; // 32 is sm3 hash size
    uint32_t outLen = sizeof(out);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    assert_non_null(ctx);

    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data1->x, data1->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data2->x, data2->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data3->x, data3->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_SUCCESS);

    assert_int_equal(outLen, 32);
    assert_int_equal(memcmp(out, hash->x, hash->len), 0);

    FreeHex(data1);
    FreeHex(data2);
    FreeHex(data3);
    FreeHex(hash);

    CRYPT_EAL_MdFreeCtx(ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static CRYPT_SM3_InOut SDV_KAE_SM3_DUP_TC001_VEC[] = {
    {
        "61626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364",
        "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732",
    },
};

static void SDV_KAE_SM3_DUP_TC001(void **state)
{
    CRYPT_SM3_InOut *test = (CRYPT_SM3_InOut *)*state;

    uint8_t output[CRYPT_SM3_DIGESTSIZE];
    uint32_t outLen = CRYPT_SM3_DIGESTSIZE;

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(test->in, msg), 0);
    assert_int_equal(ConvertHex(test->out, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sm3Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SM3, "provider=kaev1");
    assert_non_null(sm3Ctx);

    CRYPT_EAL_MdCTX *dupCtx = NULL;
    dupCtx = CRYPT_EAL_MdDupCtx(NULL);
    assert_null(dupCtx);

    dupCtx = CRYPT_EAL_MdDupCtx(sm3Ctx);
    assert_non_null(dupCtx);
    assert_int_equal(CRYPT_EAL_MdInit(dupCtx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(dupCtx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(dupCtx, output, &outLen), CRYPT_SUCCESS);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(sm3Ctx);
    CRYPT_EAL_MdFreeCtx(dupCtx);
    CRYPT_EAL_LibCtxFree(libctx);
}

int main(void)
{
    struct CMUnitTest tests[GROUP_TEST_MAX] = {NULL};
    int testCnt = 0;

    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_SM3_PROVIDER_LOAD);
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_EAL_SM3_API_TC001);
    tests[testCnt++] =
        (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SM3_FUNC_TC001, (void **)&SDV_KAE_SM3_FUNC_TC001_HEX);
    for (size_t i = 0; i < SDV_KAE_SM3_FUNC_TC002_N; i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SM3_FUNC_TC002, &SDV_KAE_SM3_FUNC_TC002_HEX[i]);
    }
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_SM3_FUNC_TC003);
    for (size_t i = 0; i < sizeof(SDV_KAE_SM3_FUNC_TC004_VEC) / sizeof(SDV_KAE_SM3_FUNC_TC004_VEC[0]); i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SM3_FUNC_TC004, &SDV_KAE_SM3_FUNC_TC004_VEC[i]);
    }
    for (size_t i = 0; i < sizeof(SDV_KAE_SM3_DUP_TC001_VEC) / sizeof(SDV_KAE_SM3_DUP_TC001_VEC[0]); i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SM3_DUP_TC001, &SDV_KAE_SM3_DUP_TC001_VEC[i]);
    }
    cmocka_run_group_tests(tests, NULL, NULL);
    return 0;
}